<template>
  <div class="snake-game">
    <h1>贪吃蛇游戏</h1>
    <div class="controls">
      <button @click="startGame" :disabled="running">开始游戏</button>
      <button @click="resetGame">重置游戏</button>
      <span>得分：{{ score }}</span>
    </div>
    <div
      class="grid"
      tabindex="0"
      @keydown.prevent="handleKey"
      @touchstart.passive="onTouchStart"
      @touchend.passive="onTouchEnd"
      ref="gameArea"
    >
      <div
        v-for="(cell, index) in width * height"
        :key="index"
        :class="{
          cell: true,
          snake: isSnakeCell(index),
          food: index === foodIndex
        }"
      ></div>
    </div>
    <div v-if="gameOver" class="overlay">
      <div class="message">
        <p>游戏结束！</p>
        <p>最终得分：{{ score }}</p>
        <button @click="resetGame">再来一次</button>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

// 游戏参数
const width = 20;
const height = 20;
const initialSnake = [2, 1, 0];
const initialDirection = 1;
const speed = 200;

// 状态
const snake = ref<number[]>([...initialSnake]);
const direction = ref<number>(initialDirection);
const foodIndex = ref<number>(0);
const score = ref<number>(0);
const running = ref<boolean>(false);
const gameOver = ref<boolean>(false);
let intervalId: number;

// 触摸坐标
let touchStartX = 0;
let touchStartY = 0;

function onTouchStart(e: TouchEvent) {
  const t = e.changedTouches[0];
  touchStartX = t.clientX;
  touchStartY = t.clientY;
}

function onTouchEnd(e: TouchEvent) {
  const t = e.changedTouches[0];
  const dx = t.clientX - touchStartX;
  const dy = t.clientY - touchStartY;
  if (Math.abs(dx) > Math.abs(dy)) {
    // 水平滑动
    if (dx > 0 && direction.value !== -1) changeDirection(1);
    else if (dx < 0 && direction.value !== 1) changeDirection(-1);
  } else {
    // 垂直滑动
    if (dy > 0 && direction.value !== -width) changeDirection(width);
    else if (dy < 0 && direction.value !== width) changeDirection(-width);
  }
}

// 随机生成食物
function generateFood() {
  let newFood: number;
  do {
    newFood = Math.floor(Math.random() * width * height);
  } while (snake.value.includes(newFood));
  foodIndex.value = newFood;
}

function isSnakeCell(idx: number) {
  return snake.value.includes(idx);
}

function calculateNextIndex(head: number) {
  return head + direction.value;
}

function step() {
  const head = snake.value[0];
  const next = calculateNextIndex(head);
  const hitWall = (
    next < 0 || next >= width * height ||
    (direction.value === 1 && next % width === 0) ||
    (direction.value === -1 && head % width === 0)
  );
  const hitSelf = snake.value.includes(next);
  if (hitWall || hitSelf) {
    endGame();
    return;
  }
  snake.value.unshift(next);
  if (next === foodIndex.value) {
    score.value++;
    generateFood();
  } else {
    snake.value.pop();
  }
}

function handleKey(e: KeyboardEvent) {
  switch (e.key) {
    case 'ArrowUp': if (direction.value !== width) changeDirection(-width); break;
    case 'ArrowDown': if (direction.value !== -width) changeDirection(width); break;
    case 'ArrowLeft': if (direction.value !== 1) changeDirection(-1); break;
    case 'ArrowRight': if (direction.value !== -1) changeDirection(1); break;
  }
}

function changeDirection(newDir: number) {
  direction.value = newDir;
}

function startGame() {
  if (running.value) return;
  running.value = true;
  gameOver.value = false;
  snake.value = [...initialSnake];
  direction.value = initialDirection;
  score.value = 0;
  generateFood();
  intervalId = window.setInterval(step, speed);
  focusGrid();
}

function endGame() {
  running.value = false;
  gameOver.value = true;
  clearInterval(intervalId);
}

function resetGame() {
  clearInterval(intervalId);
  snake.value = [...initialSnake];
  direction.value = initialDirection;
  score.value = 0;
  gameOver.value = false;
  running.value = false;
  foodIndex.value = -1;
}

const gameArea = ref<HTMLElement | null>(null);
function focusGrid() {
  gameArea.value?.focus();
}

onMounted(() => {
  generateFood();
});

onBeforeUnmount(() => {
  clearInterval(intervalId);
});
</script>

<style scoped>
.snake-game {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  position: relative;
}
.controls {
  margin-bottom: 10px;
}
.controls button {
  margin-right: 8px;
  padding: 6px 12px;
  border: none;
  background: #4caf50;
  color: white;
  border-radius: 4px;
  cursor: pointer;
}
.controls button:disabled {
  background: #9e9e9e;
  cursor: not-allowed;
}
.controls span {
  font-weight: bold;
}
.grid {
  display: grid;
  grid-template-columns: repeat(20, 1fr);
  grid-template-rows: repeat(20, 1fr);
  gap: 1px;
  outline: none;
  width: 100%;
  max-width: 400px;
  aspect-ratio: 1;
  background: #ccc;
}
.cell {
  background: #f0f0f0;
}
.snake {
  background: #4caf50;
}
.food {
  background: #f44336;
}
.overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.message {
  background: #fff;
  padding: 20px;
  border-radius: 8px;
  text-align: center;
}
.message button {
  margin-top: 10px;
  padding: 6px 12px;
  border: none;
  background: #2196f3;
  color: white;
  border-radius: 4px;
  cursor: pointer;
}
/* 移动端滑动提示 */
@media (max-width: 600px) {
  .grid {
    touch-action: none;
  }
}
</style>
